package org.opensourcephysics.controls;

import java.util.Collection;
import java.awt.Color;
import java.util.HashMap;
import java.util.Iterator;
import org.opensourcephysics.numerics.DoubleArray;
import org.opensourcephysics.numerics.IntegerArray;
import java.text.DecimalFormat;

/**
 *
 * OSPControlTable automaitally converts strings, such "pi" or "sqrt(2)" to numbers when
 * getDouble and getInt are invoked.
 *
 * @author W. Christian
 * @version 1.1
 */
public class OSPControlTable extends XMLTable implements Control {
  static Color ERROR_COLOR=Color.PINK;
  private HashMap valueCache = new HashMap();
  private boolean lockValues = false;
  private DecimalFormat format;

  /**
   * Constructs OSPControlTable and creates an XMLControlElement.
   */
  public OSPControlTable() {
    this(new XMLControlElement());
  }

  /**
   * Constructs OSPControlTable with the given control.
   * @param control XMLControlElement
   */
  public OSPControlTable(XMLControlElement control) {
    super(control);
  }


  /**
   * Locks the control's interface. Values sent to the control will not
   * update the display until the control is unlocked.
   *
   * @param lock boolean
   */
  public void setLockValues(boolean lock) {
    tableModel.control.setLockValues(lock);
    lockValues = lock;
    if(!lockValues) {
      refresh();
    }
  }


  /**
   *  Adds a parameter to the input display.
   *
   * @param  par  the parameter name
   * @param  val  the initial parameter value
   */
  public void setValue(String par, Object val){
    if(getBackgroundColor(par)== ERROR_COLOR){
      setBackgroundColor(par, Color.WHITE);
    }
     tableModel.control.setValue(par, val);
     if (!lockValues){
        refresh();
     }
  }

  /**
   * Sets the format pattern used for floating point numbers.
   * @param pattern String
   */
  public void setDecimalFormat(String pattern){
      if(pattern==null){
        format = null;
      }else{
        format = new DecimalFormat(pattern);
      }
  }


  /**
   *  Adds an initial value of a parameter to the input display.
   *
   * @param  par  the parameter name
   * @param  val  the initial parameter value
   */
  public void setValue(String par, double val){
     if(format==null){
       setValue(par, Double.toString(val));
     }else{
       setValue(par, format.format(val));
     }
     if (!Double.isNaN(val)){
        valueCache.put(par, new Double(val)); // store last good value
     }
  }

  /**
   *  Adds an initial value of a parameter to the input display.
   *
   * @param  par  the parameter name
   * @param  val  the initial parameter value
   */
  public void setValue(String par, int val) {
    setValue(par, Integer.toString(val));
    valueCache.put(par, new Double(val)); // store last good value
  }
  public void setValue(String par, boolean val) {
    if (getBackgroundColor(par) == ERROR_COLOR) {
      setBackgroundColor(par, Color.WHITE);
    }
    tableModel.control.setValue(par, val);
  }

  /**
   *  Reads a parameter value from the input display.
   *
   * @param  par
   * @return int the value of of the parameter
   */
  public int getInt(String par) {
    String str =  tableModel.control.getString(par);
    if(str==null)str=getObject(par).toString();
    // special handling for OSPCombo
    if (tableModel.control.getPropertyType(par).equals("object")) {
    	XMLControl c = tableModel.control.getChildControl(par);
    	if (c.getObjectClass() == OSPCombo.class) {
    		OSPCombo combo = (OSPCombo)c.loadObject(null);
    		return combo.getSelectedIndex();
    	}
    }
    if (str==null){
       setBackgroundColor(par, ERROR_COLOR);
       refresh();
       if (valueCache.containsKey(par)){
          return (int) ((Double) valueCache.get(par)).doubleValue();
       } else{
          return 0;
       }
     }
    Color color = (Color) cellColors.get(par);
    boolean editable = isEditable(par);
    try {
      int val = Integer.parseInt(par);
      if(editable&&color!=Color.WHITE) { // background is not correct so change it
        setBackgroundColor(par, Color.WHITE);
        refresh();
      } else if(!editable&&color!=Control.NOT_EDITABLE_BACKGROUND) { // background is not correct so change it
        setBackgroundColor(par, Control.NOT_EDITABLE_BACKGROUND);
        refresh();
      }
      valueCache.put(par, new Double(val));
      return val;
    } catch(NumberFormatException ex) {}
    try {
      int val = (int) Double.parseDouble(par);
      if(editable&&color!=Color.WHITE) { // background is not correct so change it
        setBackgroundColor(par, Color.WHITE);
        refresh();
      } else if(!editable&&color!=Control.NOT_EDITABLE_BACKGROUND) { // background is not correct so change it
        setBackgroundColor(par, Control.NOT_EDITABLE_BACKGROUND);
        refresh();
      }
      valueCache.put(par, new Double(val));
      return val;
    } catch(NumberFormatException ex) {}
    double dval = org.opensourcephysics.numerics.Util.evalMath(str);
    if(Double.isNaN(dval)&&color!=ERROR_COLOR) {
      setBackgroundColor(par, ERROR_COLOR);
      refresh();
      if(valueCache.containsKey(par)) {
        return(int) ((Double) valueCache.get(par)).doubleValue();
      } else {
        return 0;
      }
    }
    if(editable&&color!=Color.WHITE) { // background is not correct so change it
      setBackgroundColor(par, Color.WHITE);
      refresh();
    } else if(!editable&&color!=Control.NOT_EDITABLE_BACKGROUND) { // background is not correct so change it
      setBackgroundColor(par, Control.NOT_EDITABLE_BACKGROUND);
      refresh();
    }
    valueCache.put(par, new Double(dval));
    return(int) dval;
  }


  /**
   * Test if the last "get" method produced an input error.
   *
   * @param par String
   * @return boolean
   */
  public boolean inputError(String par){
    return getBackgroundColor(par)== ERROR_COLOR;
  }

  /**
   *  Reads a double value from the table.
   *
   * @param    par String the parameter key
   * @return   double the value of of the parameter
   */
  public double getDouble(String par){
     String str = tableModel.control.getString(par);
     if(str==null)str=getObject(par).toString();
     if (str==null){
        setBackgroundColor(par, ERROR_COLOR);
        refresh();
        if (valueCache.containsKey(par)){
           return ((Double) valueCache.get(par)).doubleValue();
        } else{
           return 0;
        }
     }
     Color color = (Color) cellColors.get(par);
     boolean editable = isEditable(par);
     try{
        double val = Double.parseDouble(str);
        if (editable&&color!=Color.WHITE){ // background is not correct so change it
           setBackgroundColor(par, Color.WHITE);
           refresh();
        } else if (!editable&&color!=Control.NOT_EDITABLE_BACKGROUND){ // background is not correct so change it
           setBackgroundColor(par, Control.NOT_EDITABLE_BACKGROUND);
           refresh();
        }
        valueCache.put(par, new Double(val));
        return val;
     }
     catch (NumberFormatException ex){}
     double val = org.opensourcephysics.numerics.Util.evalMath(str);
     if (Double.isNaN(val)&&color!=ERROR_COLOR){ // string is not a valid number
        setBackgroundColor(par, ERROR_COLOR);
        refresh();
     } else if (editable&&color!=Color.WHITE){ // background is not correct so change it
        setBackgroundColor(par, Color.WHITE);
        refresh();
     } else if (!editable&&color!=Control.NOT_EDITABLE_BACKGROUND){ // background is not correct so change it
        setBackgroundColor(par, Control.NOT_EDITABLE_BACKGROUND);
        refresh();
     }
     if (Double.isNaN(val)&&valueCache.containsKey(par)){
        val = ((Double) valueCache.get(par)).doubleValue();
     } else{
        valueCache.put(par, new Double(val));
     }
     return val;
  }


  /**
   * Gets the object with the specified property name.
   * Throws an UnsupportedOperationException if the named object has not been stored.
   *
   * @param  par
   * @return the object
   */
  public Object getObject(String par) throws UnsupportedOperationException {
    return tableModel.control.getObject(par);
  }

  public String getString(String par) {
    return tableModel.control.getString(par);
  }

  public boolean getBoolean(String par) {
    return tableModel.control.getBoolean(par);
  }

  public Collection getPropertyNames() {
    return tableModel.control.getPropertyNames();
  }

  /**
   *  Removes a parameter from the table.
   *
   * @param  par  the parameter name
   */
  public void removeParameter(String par){
     tableModel.control.setValue(par, null);
     setBackgroundColor(par, Color.WHITE);
  }


  public void println(String s) {
    tableModel.control.println(s);
  }

  public void println() {
    tableModel.control.println();
  }

  public void print(String s) {
    tableModel.control.print(s);
  }

  public void clearMessages() {
    tableModel.control.clearMessages();
  }

  public void clearValues() {
    tableModel.control.clearValues();
  }

  public void calculationDone(String message) {
    if(message!=null) tableModel.control.calculationDone(message);
  }

  /**
   * Returns an XML.ObjectLoader to save and load data for this object.
   *
   * @return the object loader
   */
  public static XML.ObjectLoader getLoader(){
     return new OSPControlTableLoader();
  }

  /**
* A class to save and load data for OSPControls.
*/
static class OSPControlTableLoader implements XML.ObjectLoader {

  /**
   * Saves object data to an XMLControl.
   *
   * @param control the control to save to
   * @param obj the object to save
   */
  public void saveObject(XMLControl xmlControl, Object obj) {
    OSPControlTable controlTable = (OSPControlTable) obj;
    Iterator it = controlTable.getPropertyNames().iterator();
    while(it.hasNext()) {
      String name = (String) it.next();
      Object val = controlTable.getObject(name);
      if(val.getClass()==DoubleArray.class) {
        xmlControl.setValue(name, ((DoubleArray) val).getArray());
      } else if(val.getClass()==IntegerArray.class) {
        xmlControl.setValue(name, ((IntegerArray) val).getArray());
      } else if(val.getClass()==Boolean.class) {
        xmlControl.setValue(name, ((Boolean) val).booleanValue());
      } else if(val.getClass()==Double.class) {
        xmlControl.setValue(name, ((Double) val).doubleValue());
      } else if(val.getClass()==Integer.class) {
        xmlControl.setValue(name, ((Integer) val).intValue());
      } else if(val.getClass().isArray()) {
        xmlControl.setValue(name, val);
      } else {
        xmlControl.setValue(name, val);
      }
    }
  }

  /**
   * Creates an OSPControlTable object.
   *
   * @param control the control
   * @return the newly created object
   */
  public Object createObject(XMLControl control) {
    return new OSPControlTable();
  }

  /**
   * Loads an object with data from an XMLControl.
   *
   * @param control the control
   * @param obj the object
   * @return the loaded object
   */
  public Object loadObject(XMLControl control, Object obj) {
    OSPControlTable controlTable = (OSPControlTable) obj;
    // iterate over properties and add them to table model
    Iterator it = control.getPropertyNames().iterator();
    controlTable.setLockValues(true);
    while(it.hasNext()) {
      String name = (String) it.next();
      if(control.getPropertyType(name).equals("string")) {
        controlTable.setValue(name, control.getString(name));
      } else if(control.getPropertyType(name).equals("int")) {
        controlTable.setValue(name, control.getInt(name));
      } else if(control.getPropertyType(name).equals("double")) {
        controlTable.setValue(name, control.getDouble(name));
      } else if(control.getPropertyType(name).equals("boolean")) {
        controlTable.setValue(name, control.getBoolean(name));
      } else {
        controlTable.setValue(name, control.getObject(name));
      }
    }
    controlTable.setLockValues(false);
    return obj;
  }
}

}
